﻿//  --------------------------------
//  Copyright (c) AW Systems, Inc. All rights reserved.
//  This source code is made available under the terms of the Microsoft Public License (Ms-PL)
//  http://www.codeplex.com/SLAM/license
//  ---------------------------------

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Data;
using System.Data.Linq;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using AWS.SLAM.Configuration;
using AWS.SLAM.Hierarchy.Data;
using AWS.SLAM.SharePoint;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.WebControls;
using System.Threading;

namespace AWS.SLAM.Hierarchy
{
    public class HierarchyListView : WebPart
    {
        private SPList _list;
        private DataMapping _currentDataMapping;

        protected override void CreateChildControls()
        {
            //Check whether SLAM is setup, first if it's installed then if it is activated
            if (!InstallationValidator.VerifySlamIsInstalled())
            {
                Label installedLabel = new Label();
                installedLabel.Text = "SharePoint List Association Manager (SLAM) is required to use the SLAM Hierarchy List View and it is not installed.  Please download SLAM from CodePlex at http://slam.codeplex.com/ then install and set it up prior to attempting to use this webpart again.";
                Controls.Add(installedLabel);
            }
            else if (!InstallationValidator.VerifySlamIsActivated())
            {
                Label activatedLabel = new Label();
                activatedLabel.Text = "SharePoint List Association Manager (SLAM) feature must be activated to use the SLAM Hierarchy List View and it is not.  Please activate the SharePoint List Association Manager feature and ensure that SLAM is active.  SLAM can be activated in the SLAM Control Panel, via Activation then clicking \"Activate\".";
                Controls.Add(activatedLabel);
            }
            else
            {
                //If SLAM is ready, get configuration DataMapping
                SPContext context = SPContext.GetContext(HttpContext.Current);

                string siteCollection = context.Site.ServerRelativeUrl.TrimStart('/');
                IConfigurationManager slamConfiguration = ComponentService.GetInstance<IConfigurationManager>();
                _currentDataMapping = slamConfiguration.DataMapping;
                if (!String.IsNullOrEmpty(siteCollection) && slamConfiguration.DataMappings.ContainsKey(siteCollection))
                {
                    _currentDataMapping = slamConfiguration.DataMappings[siteCollection];
                }

                //Create the Grid
                
                _list = context.List;

                SPGridView hierarchyGridView = new SPGridView();
                hierarchyGridView.AutoGenerateColumns = false;

                //Create menu with view, edit and manage permissions options
                SPMenuField hierarchyField = new SPMenuField();
                hierarchyField.NavigateUrlFormat = _list.Forms[PAGETYPE.PAGE_DISPLAYFORM].ServerRelativeUrl + "?ID={0}";
                hierarchyField.NavigateUrlFields = "ID";
                hierarchyField.MenuTemplateId = "ActionsMenu";
                hierarchyField.UseMaximumWidth = true;
                hierarchyField.TokenNameAndValueFields = "ID=ID";

                MenuTemplate actionsMenu = new MenuTemplate();
                actionsMenu.ID = "ActionsMenu";
                actionsMenu.UseShortId = true;

                MenuItemTemplate viewItem = new MenuItemTemplate("View Item");
                viewItem.ClientOnClickScript = "STSNavigate('" + _list.Forms[PAGETYPE.PAGE_DISPLAYFORM].ServerRelativeUrl + "?ID=%ID%&Source=" + HttpContext.Current.Request.Url.ToString() + "')";
                viewItem.Permissions = SPBasePermissions.ViewListItems;
                actionsMenu.Controls.Add(viewItem);

                MenuItemTemplate editItem = new MenuItemTemplate(
                    "Edit Item", "/_layouts/images/EDITITEM.GIF");
                editItem.ClientOnClickScript = "STSNavigate('" + _list.Forms[PAGETYPE.PAGE_EDITFORM].ServerRelativeUrl + "?ID=%ID%&Source=" + HttpContext.Current.Request.Url.ToString() + "')";
                editItem.Permissions = SPBasePermissions.EditListItems;
                actionsMenu.Controls.Add(editItem);

                MenuItemTemplate managePermItem = new MenuItemTemplate(
                    "Manage Permissions", "/_layouts/images/manageperm.gif");
                managePermItem.ClientOnClickScript = "NavigateToManagePermsPage('" + SPHttpUtility.EcmaScriptStringLiteralEncode(context.Site.MakeFullUrl(_list.ParentWebUrl)) + "', '" + _list.ID.ToString("B") + "','%ID%')";
                managePermItem.Permissions = SPBasePermissions.ManagePermissions;
                actionsMenu.Controls.Add(managePermItem);

                //MenuItemTemplate deleteItem = new MenuItemTemplate(
                //    "Delete Item", "/_layouts/images/delitem.gif");
                //deleteItem.ClientOnClickUsingPostBackEvent = "__page,%ID%";
                //deleteItem.Permissions = SPBasePermissions.DeleteListItems;
                //actionsMenu.Controls.Add(deleteItem);

                this.Controls.Add(actionsMenu);

                //Create DataTable to bind to the Grid
                string itemDisplayName = "Title";
                string hierarchyFieldName = "";
                DataTable itemsTable = new DataTable();
                DataColumn idColumn = itemsTable.Columns.Add("ID");

                itemsTable.PrimaryKey = new DataColumn[] { idColumn };

                Dictionary<string, string> gridColumns = new Dictionary<string, string>();

                //Find the HierarchyPositionField, this view is pretty meaningless without it
                foreach (SPField field in _list.Fields)
                {
                    if (field is HierarchyPositionField)
                    {
                        hierarchyFieldName = field.Title;
                        itemDisplayName = ((HierarchyPositionField)field).ItemDisplayName;
                        hierarchyField.HeaderText = field.Title;
                        hierarchyField.TextFields = field.Title;
                        itemsTable.Columns.Add(field.Title);

                        gridColumns.Add(itemDisplayName, field.Title);

                        //Add rows to the DataTable after retrieving the hierarchy from the
                        //SLAM database
                        Dictionary<int, string> parentageDictionary = GetParentageDictionary(itemDisplayName);
                        foreach (KeyValuePair<int, string> item in parentageDictionary)
                        {
                            DataRow row = itemsTable.NewRow();
                            row["ID"] = item.Key;
                            row[field.Title] = item.Value;
                            itemsTable.Rows.Add(row);
                        }
                        break;
                    }
                }

                hierarchyGridView.Columns.Add(hierarchyField);

                SPWeb currentWeb = SPContext.Current.Web;
                currentWeb.AllowUnsafeUpdates = true;

                SPView view = _list.DefaultView;

                string groupBy = "";
                string sortBy = "";
                string groupByName = "";
                string sortByName = "";
                bool sortAscending = true;
                bool groupCollapse = true;

                //This List needs a View, if it doesn't have one
                //make one called Hierarchy Field View that at least contains
                //the item display name
                if (view == null)
                {
                    StringCollection viewFields = new StringCollection();
                    viewFields.Add(itemDisplayName);

                    if (_list.Views.Count == 0)
                    {
                        bool newViewExists = true;
                        try
                        {
                            newViewExists = _list.Views["Hierarchy Field View"] != null;
                        }
                        catch
                        {
                            newViewExists = false;
                        }
                        view = _list.Views.Add("Hierarchy Field View", viewFields, "", 200000, false, true);
                    }
                }
                else
                {
                    foreach (Match match in Regex.Matches(view.Query, "\\<GroupBy.*?((?<=Name=\").*?(?=\")).*?\\/GroupBy\\>"))
                    {
                        foreach (Group group in match.Groups)
                        {
                            if (!group.Value.Contains("<GroupBy"))
                            {
                                groupBy = group.Value;
                            }
                        }
                    }

                    foreach (Match match in Regex.Matches(view.Query, "\\<GroupBy.*?((?<=Collapse=\").*?(?=\")).*?\\/GroupBy\\>"))
                    {
                        foreach (Group group in match.Groups)
                        {
                            if (!group.Value.Contains("<GroupBy"))
                            {
                                groupCollapse = group.Value.ToLower() == "true";
                            }
                        }
                    }

                    foreach (Match match in Regex.Matches(view.Query, "\\<OrderBy.*?((?<=Name=\").*?(?=\")).*?\\/OrderBy\\>"))
                    {
                        foreach (Group group in match.Groups)
                        {
                            if (!group.Value.Contains("<OrderBy"))
                            {
                                sortBy = group.Value;
                            }
                        }
                    }

                    foreach (Match match in Regex.Matches(view.Query, "\\<OrderBy.*?((?<=Ascending=\").*?(?=\")).*?\\/OrderBy\\>"))
                    {
                        foreach (Group group in match.Groups)
                        {
                            if (!group.Value.Contains("<OrderBy"))
                            {
                                sortAscending = group.Value.ToLower() == "true";
                            }
                        }
                    }

                    //If we have a view, add columns to the Grid
                    //for extra fields
                    List<string> addedColumns = new List<string>();

                    if (!String.IsNullOrEmpty(groupBy) || !String.IsNullOrEmpty(sortBy))
                    {
                        foreach (SPField field in _list.Fields)
                        {
                            if (field.InternalName == groupBy)
                            {
                                groupByName = field.Title;
                                if ((field.Title != hierarchyFieldName) &&
                                    (field.InternalName != itemDisplayName) &&
                                    !itemsTable.Columns.Contains(field.Title))
                                {
                                    itemsTable.Columns.Add(field.Title);
                                    addedColumns.Add(field.Title);
                                }
                                else if (groupBy == itemDisplayName)
                                {
                                    groupByName = hierarchyFieldName;
                                }
                            }

                            if (field.InternalName == sortBy)
                            {
                                sortByName = field.Title;
                                if ((field.Title != hierarchyFieldName) &&
                                    (field.InternalName != itemDisplayName) &&
                                    !itemsTable.Columns.Contains(field.Title))
                                {
                                    itemsTable.Columns.Add(field.Title);
                                    addedColumns.Add(field.Title);
                                }
                                else if(sortBy == itemDisplayName)
                                {
                                    sortByName = hierarchyFieldName;
                                }
                            }
                        }
                    }
                    
                    foreach (string fieldName in view.ViewFields)
                    {
                        SPField field = _list.Fields.GetFieldByInternalName(fieldName);
                        if ((field.Title != hierarchyFieldName) &&
                            (field.InternalName != itemDisplayName) &&
                            !itemsTable.Columns.Contains(field.Title))
                        {
                            //gridColumns.Add(field.InternalName, field.Title);

                            SPBoundField boundField = new SPBoundField();
                            boundField.HeaderText = field.Title;
                            boundField.DataField = field.Title;
                            hierarchyGridView.Columns.Add(boundField);
                            itemsTable.Columns.Add(field.Title);
                            addedColumns.Add(field.Title);
                        }
                    }

                    foreach (SPListItem listItem in _list.Items)
                    {
                        DataRow row = itemsTable.Rows.Find(listItem.ID);
                        if (row != null)
                        {
                            foreach (string columnName in addedColumns)
                            {
                                if (listItem.Fields.ContainsField(columnName))
                                {
                                    try
                                    {
                                        if (columnName == groupByName || columnName == sortByName && listItem[columnName].ToString().Contains(";#"))
                                        {
                                            row[columnName] = Regex.Split(listItem[columnName].ToString(), ";#")[1];
                                        }
                                        else
                                        {
                                            row[columnName] = listItem[columnName];
                                        }
                                        
                                    }
                                    catch { }
                                }
                            }
                        }
                        else
                        {
                            DataRow newRow = itemsTable.NewRow();
                            newRow["ID"] = listItem.ID;
                            if (itemsTable.Columns.Contains(hierarchyFieldName) && listItem.Fields.ContainsField(itemDisplayName))
                            {
                                newRow[hierarchyFieldName] = listItem[itemDisplayName];
                            }

                            foreach (string columnName in addedColumns)
                            {
                                if (listItem.Fields.ContainsField(columnName))
                                {
                                    try
                                    {
                                        if (columnName == groupByName || columnName == sortByName && listItem[columnName].ToString().Contains(";#"))
                                        {
                                            newRow[columnName] = Regex.Split(listItem[columnName].ToString(), ";#")[1];
                                        }
                                        else
                                        {
                                            newRow[columnName] = listItem[columnName];
                                        }
                                    }
                                    catch { }
                                }
                            }
                            itemsTable.Rows.Add(newRow);
                        }
                    }
                }
                

                

                //Bind the Grid
                if (!String.IsNullOrEmpty(groupByName))
                {   
                    hierarchyGridView.GroupField = groupByName;
                    hierarchyGridView.AllowGrouping = true;
                    hierarchyGridView.AllowGroupCollapse = true;
                    hierarchyGridView.GroupFieldDisplayName = groupByName;
                    hierarchyGridView.GroupImageDescriptionField = groupByName;

                    itemsTable.DefaultView.Sort = groupByName + " ASC";
                }

                if (!String.IsNullOrEmpty(sortByName))
                {
                    if (String.IsNullOrEmpty(itemsTable.DefaultView.Sort))
                    {
                        itemsTable.DefaultView.Sort = sortByName + " " + (sortAscending ? "ASC" : "DESC");
                    }
                    else
                    {
                        itemsTable.DefaultView.Sort += ", " + sortByName + " " + (sortAscending ? "ASC" : "DESC");
                    }
                }

                hierarchyGridView.DataSource = itemsTable.DefaultView;
                hierarchyGridView.DataBind();

                //Add the default List Toolbar for this List's Context
                ViewToolBar toolbar = new ViewToolBar();
                SPContext newContext = SPContext.GetContext(this.Context, _list.DefaultView.ID, _list.ID, currentWeb);
                toolbar.RenderContext = newContext;
                Controls.Add(toolbar);

                Controls.Add(hierarchyGridView);
            }
            base.CreateChildControls();
        }

        private Dictionary<int, string> GetParentageDictionary(string itemDisplayName)
        {
            string dataSchema = "";
            string tableName = "";
            string connectionString = "";
            IConfigurationManager slamConfiguration = ComponentService.GetInstance<IConfigurationManager>();
            if (slamConfiguration is XmlConfigurationManager)
            {
                dataSchema = ((XmlSqlDataMapping)_currentDataMapping).GetDataSchema() + ".";
            }

            ConfiguredType type = FindSlamConfiguredType(slamConfiguration);
            Dictionary<int, string> parentageDictionary = new Dictionary<int, string>();
            if (type != null)
            {
                tableName = type.Name;
                string columnName = itemDisplayName;

                if (type is SqlConfiguredType)
                {
                    SqlConfiguredType sqlType = (SqlConfiguredType)type;
                    if (!String.IsNullOrEmpty(sqlType.Table))
                    {
                        tableName = sqlType.Table;
                    }

                    foreach (Field field in _currentDataMapping.GetTypeFields(type.Id, type.Name))
                    {
                        if (field.Name == columnName && field is SqlMappedField)
                        {
                            SqlMappedField sqlField = (SqlMappedField)field;
                            columnName = String.IsNullOrEmpty(sqlField.Column) ? field.Name : sqlField.Column;
                            break;
                        }
                    }
                }

                connectionString = "";
                if (_currentDataMapping is XmlSqlDataMapping)
                {
                    connectionString = ((XmlSqlDataMapping)_currentDataMapping).GetConnectionString();
                }

                if (String.IsNullOrEmpty(connectionString))
                {
                    connectionString = slamConfiguration.GetSection<ConnectionStringSection>("ConnectionStrings").Default;
                }
                else
                {
                    connectionString = slamConfiguration.GetSection<ConnectionStringSection>("ConnectionStrings").GetConnectionString(connectionString);
                }
                
                
                DataService dataService = new DataService(connectionString, dataSchema.TrimEnd('.'));
                string itemQuery = String.Format("select ListRelativeID, {0} Name from {1}{2} ", GetTableName(columnName), dataSchema, GetTableName(tableName));
                if (dataService.VerifyHierarchyColumnsExist(tableName))
                {
                    DataContext dataContext = new DataContext(connectionString);
                    Stack<string> parentage = new Stack<string>();
                    Stack<int> parentRights = new Stack<int>();

                    foreach (var hierarchyItem in dataContext.ExecuteQuery<HierarchyDisplayItem>(String.Format("select ListRelativeID, {0} Name, isnull(Lft, 1000000000) [Left], isnull(Rght, 1000000000) [Right] from {1}{2} where ListID = '{3}' order by isnull(Lft, 1000000000) ", GetTableName(columnName), dataSchema, GetTableName(tableName), _list.ID.ToString())))
                    {
                        while (parentRights.Count > 0 && hierarchyItem.Left >= parentRights.Peek())
                        {
                            parentage.Pop();
                            parentRights.Pop();
                        }

                        parentage.Push(hierarchyItem.Name);
                        parentRights.Push(hierarchyItem.Right);

                        //parentageDictionary.Add(hierarchyItem.ListRelativeID, String.Format("<a href=\"{0}\">{1}</a>", _list.Forms[PAGETYPE.PAGE_DISPLAYFORM].ServerRelativeUrl + "?ID=" + hierarchyItem.ListRelativeID.ToString(), String.Join(" > ", parentage.Reverse().ToArray())));
                        parentageDictionary.Add(hierarchyItem.ListRelativeID, String.Join(" > ", parentage.Reverse().ToArray()));
                    }
                }
                else
                {
                    DataContext dataContext = new DataContext(connectionString);

                    foreach (var hierarchyItem in dataContext.ExecuteQuery<HierarchyDisplayItem>(String.Format("select ListRelativeID, {0} Name from {1}{2} where ListID = '{3}' ", GetTableName(columnName), dataSchema, GetTableName(tableName), _list.ID.ToString())))
                    {
                        parentageDictionary.Add(hierarchyItem.ListRelativeID, hierarchyItem.Name);
                    }
                }
            }
            return parentageDictionary;
        }

        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            
            //If the Referrer is not empty and it contains Source we can be reasonably sure
            //that Source value brought the Request to the page containing this WebPart.
            //It is also very likely that a page using Source to redirect to the page containing
            //this WebPart probably involved a process to change the hierarchy being represented here.
            if (Context.Request.UrlReferrer != null && Context.Request.UrlReferrer.Query.Contains("Source"))
            {
                //Just in case the SLAM event handler doesn't fire right away after saving
                //or it doesn't finish before coming back to this page, wait for a second
                Thread.Sleep(1000);
            }

        }

        private ConfiguredType FindSlamConfiguredType(IConfigurationManager configuration)
        {
            ConfiguredType configuredType = null;
            bool configuredContentType = false;
            foreach (SPEventReceiverDefinition receiver in _list.EventReceivers)
            {
                if (receiver.Name.StartsWith("SLAM") && receiver.Class.Contains("ContentTypeEventManager"))
                {
                    configuredContentType = true;
                }
            }

            foreach (ConfiguredType type in _currentDataMapping.GetAllTypes())
            {
                bool contentTypeFound = false;
                foreach (SPContentType contentType in _list.ContentTypes)
                {
                    if (contentType.EqualsOrInherits(type.Name))
                    {
                        contentTypeFound = true;
                        break;
                    }
                }

                if (configuredContentType && type.ContentType && contentTypeFound)
                {
                    configuredType = type;
                    break;
                }
                else if (!configuredContentType && (type.Id == _list.ID.ToString() || type.Name == _list.Title))
                {
                    configuredType = type;
                    break;
                }
            }

            return configuredType;
        }

        private string GetTableName(string typeName)
        {
            return Regex.Replace(typeName, "[\\W\\s]", "");
        }
    }
}
